// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <stdexcept>
#include <vector>

namespace coin2::base {

template <typename T>
class Ring {
 public:
  explicit Ring(size_t capacity) : capacity_(capacity), buffer_(capacity) {}

  bool empty() { return size_ == 0; }
  bool non_empty() { return size_ > 0; }
  bool is_full() { return size_ == capacity_; }
  size_t size() { return size_; }

  const T& front() {
    if (empty()) {
      throw std::runtime_error("Ring.front(): the ring is empty");
    }
    return buffer_[front_pointer_];
  }
  const T& back() {
    if (empty()) {
      throw std::runtime_error("Ring.back(): the ring is empty");
    }
    return buffer_[back_pointer_];
  }

  void push_back(T item) {
    if (empty()) {
      size_ = 1;
      front_pointer_ = 0;
      back_pointer_ = 0;
      buffer_[0] = item;
    } else if (is_full()) {
      front_pointer_ = (front_pointer_ + 1) % capacity_;
      back_pointer_ = (back_pointer_ + 1) % capacity_;
      buffer_[back_pointer_] = item;
    } else {
      size_++;
      back_pointer_ = (back_pointer_ + 1) % capacity_;
      buffer_[back_pointer_] = item;
    }
  }

  void pop_front() {
    if (empty()) {
      throw std::runtime_error("Ring.pop_front(): the ring is empty");
    }
    size_--;
    front_pointer_ = (front_pointer_ + 1) % capacity_;
  }

  void pop_back() {
    if (empty()) {
      throw std::runtime_error("Ring.pop_back(): the ring is empty");
    }
    size_--;
    back_pointer_ = (back_pointer_ + capacity_ - 1) % capacity_;
  }

  void DebugPrint() {
    printf("size: %ld, [", size_);
    for (auto i = front_pointer_;; i = (i + 1) % capacity_) {
      printf("%ld, ", buffer_[i]);
      if (i == back_pointer_) {
        break;
      }
    }
    printf("]\n");
  }

 private:
  const size_t capacity_;
  std::vector<T> buffer_;

  size_t size_ = 0;
  int front_pointer_ = 0;
  int back_pointer_ = 0;
};

}  // namespace coin2::base
